/*
 * pqGUI.java
 * Software Engineering Lab 2
 * Oct 10, 2006
 * 
 * By:
 * Dan Grissom - grissodt@email.uc.edu
 * Jon Hoffman - hoffmaj1@email.uc.edu
 * Jon Nafziger - nafzigjw@email.uc.edu
 * Mike Schmidt - schmidm2@email.uc.edu
 * 
 * pqGUI is a Graphical User Interface for our priority queue application
 * 
 * Some Swing example code by Sun, http://java.sun.com/docs/books/tutorial/uiswing/learn/examples/SwingApplication.java
 */

package pqGUI;

import javax.swing.*;          
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.border.LineBorder;



import javax.swing.ImageIcon;

import pQueue.pQueue;
import Event.Event;

public class pqGUI implements ActionListener {
	// Create pQueue, initialize random
	private static pQueue priorityQueue = new pQueue();
	private static Random generator = new Random();
	
	// Boolean used to track whether the user pressed stop
	private static Boolean stopEnqueueing = false;
	
	// Timer used to process Events while playing
	private Timer runTimer;

	// We need access to these objects in the event handler
	// So make them properties of the class
	private static JTextArea statusText;
	private static JLabel curExecPR;
	private static JLabel firstGenPR;
	private static JLabel secondGenPR;
	private static JLabel curExecINT;
	private static JLabel firstGenINT;
	private static JLabel secondGenINT;
	private static JButton btnStep;
	private static JButton btnStop;
	private static JButton btnPlay;
	private static JButton btnPause;
	private static JTextArea eventEntryINF;
	private static JTextArea eventEntryPR;
	private static JButton btnAddEvent;

    /**  Create the GUI components, and add them all to a single JPanel. */
    public Component createComponents() {
    	// The GUI is made up of nested JPanels
    	
        JPanel buttonPane = new JPanel(new GridLayout(1, 0));
        
        // Use icons with the buttons to make the interface more attractive
        ImageIcon playIcon = new ImageIcon(ClassLoader.getSystemResource("Icons/Play.png"));
        ImageIcon pauseIcon = new ImageIcon(ClassLoader.getSystemResource("Icons/Pause.png"));
        ImageIcon stopIcon = new ImageIcon(ClassLoader.getSystemResource("Icons/Stop.png"));
        ImageIcon stepIcon = new ImageIcon(ClassLoader.getSystemResource("Icons/Step.png"));
        
        //Create the buttons
        
        btnPlay = new JButton("Play", playIcon);
        btnPlay.setMnemonic(KeyEvent.VK_Y);
        btnPlay.setActionCommand("play");
        btnPlay.addActionListener(this);
        buttonPane.add(btnPlay);
        
        btnPause = new JButton("Pause", pauseIcon);
        btnPause.setMnemonic(KeyEvent.VK_E);
        btnPause.addActionListener(this);
        btnPause.setActionCommand("pause");
        btnPause.setEnabled(false);
        buttonPane.add(btnPause);
        
        btnStep = new JButton("Step", stepIcon);
        btnStep.setMnemonic(KeyEvent.VK_T);
        btnStep.addActionListener(this);
        btnStep.setActionCommand("step");
        buttonPane.add(btnStep);

        btnStop = new JButton("Stop", stopIcon);
        btnStop.setMnemonic(KeyEvent.VK_S);
        btnStop.addActionListener(this);
        btnStop.setActionCommand("stop");
        btnStop.setEnabled(false);
        buttonPane.add(btnStop);

        buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
        
        // Create JPanel for entering user defined events.
        
        JPanel eventEntryPane = new JPanel(new GridLayout(2,3));
        
        JLabel entryLabelINF = new JLabel("New event name");
        eventEntryPane.add(entryLabelINF);
        
        JLabel entryLabelPR = new JLabel("New event priority");
        eventEntryPane.add(entryLabelPR);
        
        JLabel entryLabelBlank = new JLabel("");
        eventEntryPane.add(entryLabelBlank);
        
        eventEntryINF = new JTextArea("",1,0);
        eventEntryINF.setBorder(new LineBorder(Color.black));
        eventEntryPane.add(eventEntryINF);
        
        eventEntryPR = new JTextArea("",1,0);
        eventEntryPR.setBorder(new LineBorder(Color.black));
        eventEntryPane.add(eventEntryPR);
        
        btnAddEvent = new JButton("Add Event");
        btnAddEvent.setMnemonic(KeyEvent.VK_A);
        btnAddEvent.addActionListener(this);
        btnAddEvent.setActionCommand("addEvent");
        btnAddEvent.setEnabled(true);
        eventEntryPane.add(btnAddEvent);

        eventEntryPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
        
        
        // Create the JLabels which will be used to show the user what is happening
        
        JPanel statusPane = new JPanel(new GridLayout(3,3));
        
        JLabel curExecTitle = new JLabel("Executed:");
        statusPane.add(curExecTitle);

        JLabel firstGenTitle = new JLabel("First Generated Event:");
        statusPane.add(firstGenTitle);

        JLabel secondGenTitle = new JLabel("Second Generated Event:");
        statusPane.add(secondGenTitle);
        
        curExecPR = new JLabel();
        statusPane.add(curExecPR);
        
        firstGenPR = new JLabel();
        statusPane.add(firstGenPR);
        
        secondGenPR = new JLabel();
        statusPane.add(secondGenPR);
        
        curExecINT = new JLabel();
        statusPane.add(curExecINT);
        
        firstGenINT = new JLabel();
        statusPane.add(firstGenINT);
        
        secondGenINT = new JLabel();
        statusPane.add(secondGenINT);
        
        statusPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));

        // Use a JTextArea to display a log
        statusText = new JTextArea("",8,0);

        JPanel testPane = new JPanel(new GridLayout(1,0));
        testPane.add(new JScrollPane(statusText));

        testPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));

        // This is the main JPanel, add the other panels to it
        JPanel mainPane = new JPanel();
        // Use BoxLayout.PAGE_AXIS so the other panels aren't stretched vertically
        mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.PAGE_AXIS));
        mainPane.add(buttonPane);
        mainPane.add(eventEntryPane);
        mainPane.add(statusPane);
        mainPane.add(testPane);
        
        mainPane.setBorder(BorderFactory.createEmptyBorder(10, 30, 10, 30));
        return mainPane;
    }


    /**
     * Event handler for the buttons.
     */
    public void actionPerformed(ActionEvent e) {
        if ("play".equals(e.getActionCommand()))
        {
        	//This is the ActionListener that will be called when the timer is executed
        	ActionListener runListener = new ActionListener(){
        		public void actionPerformed(ActionEvent evt)
        		{
        			if (!priorityQueue.isEmpty())
        			{
        				// We're playing, so process a single Event
        				processEvent();
        			}
        			else
        			{
        				// We're done. Stop the timer and inform the user
        	        	runTimer.stop();
        				log("Finished processing.");
        				log("____________________");
        	        	btnStep.setEnabled(false);
        	        	btnStop.setEnabled(false);
        	        	btnPlay.setEnabled(false);
        	        	btnPause.setEnabled(false);
        	        	btnAddEvent.setEnabled(false);
        	        	eventEntryPR.setEnabled(false);
        	        	eventEntryINF.setEnabled(false);
        	        	curExecPR.setText("");
        	        	curExecINT.setText("");
        	        	clearEnqueuedDisplay();
        			}
        		}
        	};
        	// Start the play timer
        	runTimer = new Timer(200, runListener);
        	runTimer.start();
        	btnStep.setEnabled(false);
        	btnStop.setEnabled(true);
        	btnPlay.setEnabled(false);
        	btnPause.setEnabled(true);
        	btnAddEvent.setEnabled(false);
        	eventEntryPR.setEnabled(false);
        	eventEntryINF.setEnabled(false);
        }
        else if ("pause".equals(e.getActionCommand()))
        {
        	// Stop the timer. The user can now either step or play.
        	runTimer.stop();
        	btnStep.setEnabled(true);
        	btnStop.setEnabled(false);
        	btnPlay.setEnabled(true);
        	btnPause.setEnabled(false);
        	btnAddEvent.setEnabled(true);
        	eventEntryPR.setEnabled(true);
        	eventEntryINF.setEnabled(true);
        }
        else if ("step".equals(e.getActionCommand()))
        {
        	// Only process a single event
        	processEvent();
        }
        else if ("stop".equals(e.getActionCommand()))
        {
        	// Here, stop means "Stop generating Events."
        	//Continue the timer, but don't enqueue any more Events
        	stopEnqueueing = true;
        	log("Stopping. No new events will be created.");
        	btnStep.setEnabled(false);
        	btnStop.setEnabled(false);
        	btnPlay.setEnabled(false);
        	btnPause.setEnabled(true);
        	btnAddEvent.setEnabled(false);
        	eventEntryPR.setEnabled(false);
        	eventEntryINF.setEnabled(false);
        }
        else if ("addEvent".equals(e.getActionCommand()))
        {
        	try
        	{
        		if (eventEntryINF.getText().length() != 10)
	        		throw new Exception("INF length is not 10");
        		int userPriority = Integer.parseInt(eventEntryPR.getText());
        		if (userPriority < 0 || userPriority > 200)
        		{
            		log("Failed to add event!\nEvent name must be 10 characters.\nPriority must be a valid integer.");
            		return;
        		}
        		Event userEvent = new Event(eventEntryINF.getText(),userPriority);
        		priorityQueue.enqueue(userEvent);
        		log("Enqueued user event: " + userEvent);
        	}
        	catch( Exception ex)
        	{
        		log("Failed to add event!\nEvent name must be 10 characters.\nPriority must be a valid integer.\nPriority must be between 0 and 200.");
        	}
        }
    }


    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Set the look and feel.
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception e) {
        	// If setting the look and feel failed, schedule a log to inform the user
        	final String stackTrace = e.toString();
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    log("Error: Couldn't get System look and feel. Using default.\n" + stackTrace);
                }
            });
        }

        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);

        //Create and set up the window.
        JFrame frame = new JFrame("pqGUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        pqGUI app = new pqGUI();
        Component contents = app.createComponents();
        frame.getContentPane().add(contents, BorderLayout.CENTER);
        frame.setResizable(false);
        
        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
                initializePQueue();
            }
        });
    }

    /** Initialize the contents of the queue */
    protected static void initializePQueue()
    {
    	// Create 20 new events of the format Event_####, Probability between 0 and 200
    	log("Generating 20 events.");
    	for (int i = 0; i < 20; i++)
    	{
    		int randIntValue = generator.nextInt(201);
    		Event newEvent = new Event(genRandomINF(), randIntValue);
    		priorityQueue.enqueue(newEvent);
        	log("Enqueued: " + newEvent);
    	}
    	// Instruct the user to press Play or Pause
    	log("\nDone generating events.\nPress Play to continuously process events.\nOr\nPress Step to process a single event.");
    }

    /** Processes a single event from the priority queue */
    protected static void processEvent()
    {
    	if (!priorityQueue.isEmpty())
    	{
    		// Then dequeue an Event
	    	int newPR;
	    	Event newEvent;
	    	Boolean isSecondEnqueue = false;
	    	Event event = priorityQueue.dequeue();
	    	displayDequeued(event);
	    	clearEnqueuedDisplay();
	        log("Dequeued: " + event);
	        if (!stopEnqueueing)
	        {
	        	// If the user hasn't pressed Stop, find out if we should generate new Events
		        if (generator.nextFloat() <= 0.5)
		        {
		        	// Generate the new 0.5 probability event
		        	newPR = event.PR + (int)(0.5*(200-event.PR));
		        	newEvent = new Event(genRandomINF(), newPR);
		        	priorityQueue.enqueue(newEvent);
		        	log("Enqueued: " + newEvent);
			    	displayEnqueued(event,isSecondEnqueue);
			    	isSecondEnqueue = true;
		        }
		        if (generator.nextFloat() <= 0.3)
		        {
		        	// Generate the new 0.3 probability event
		        	newPR = event.PR + (int)(0.7*(200-event.PR));
		        	newEvent = new Event(genRandomINF(), newPR);
		        	priorityQueue.enqueue(newEvent);
		        	log("Enqueued: " + newEvent);
			    	displayEnqueued(event,isSecondEnqueue);
		        }
	        }
    	}
    	else
    	{
    		// We should not get here, since this function isn't called when the queue is empty
    		log("Priority Queue is empty.");
    	}
    }

    /** Displays a message to the user on the JTextArea */
    protected static void log(String logItem)
    {
    	statusText.append(logItem + "\n");
    }

    /** Reset the enqueue JLabels */
    protected static void clearEnqueuedDisplay()
    {
		firstGenPR.setText("");
		firstGenINT.setText("");
		secondGenPR.setText("");
		secondGenINT.setText("");
    }

    /** Show the dequeued Event on the GUI */
    protected static void displayDequeued(Event dequeuedEvent)
    {
    	curExecPR.setText("  " + Integer.toString(dequeuedEvent.PR));
    	curExecINT.setText("  " + dequeuedEvent.INF);
    }

    /** Show the enqueued Event on the GUI */
    protected static void displayEnqueued(Event enqueuedEvent, Boolean secondEnqueued)
    {
    	if (!secondEnqueued)
    	{
    		//This is the first Event generated in this iteration
    		firstGenPR.setText("  " + Integer.toString(enqueuedEvent.PR));
    		firstGenINT.setText("  " + enqueuedEvent.INF);
    	}
    	else
    	{
    		//This is the second Event generated in this iteration
    		secondGenPR.setText("  " + Integer.toString(enqueuedEvent.PR));
    		secondGenINT.setText("  " + enqueuedEvent.INF);
    	}
    }

    /** Generates a random INF string */
    protected static String genRandomINF()
    {
    	// Generate a number between 1000 and 9999 so it is always 4 digits long
    	int randFourDigits = generator.nextInt(1000)+(generator.nextInt(9)+1)*1000;
    	return "Event_" + randFourDigits;
    }
    
}
